AgentCoreのCI/CDパイプラインにフックを使って検証ゲートをはさむ

AIエージェントのデプロイパイプラインを考える

みなさん、AIエージェント作ってますか?
AWS上でAIエージェントをデプロイするのに、Amazon Bedrock AgentCore(以下、AgentCore)は、もはやなくてはならないサービスですね。
PoCであればAgentCore CLIでデプロイしていたAIエージェントも、プロダクション環境となると、他のアプリケーションと同様にリポジトリでソース管理されていて、ブルーグリーンデプロイしたい場面が訪れるのではないかと思います。
今回は、GitHubでソース管理されているAIエージェントをAgentCore Runtimeにブルーグリーンデプロイするという想定で考えていきます。
最終的にやりたいことは以下の2つです。

  • 不具合のあるビルドを公開する前に止める
  • 品質が保てているかの判定をする

AIエージェントにおいてやっかいなのは、HTTP 200を返していても、正常に動いているかは保証できないところかなと思います。
ただ、いきなりそこまでもっていくのは大変なので、今回は前者の「不具合のあるビルドを公開する前に止める」までをやります。

AgentCoreだけでは組めない

AgentCore Runtimeにはバージョンがあり、エンドポイントが特定のバージョンを指します。
これで切り替えられそうかなと思ったのですが、以下の2つの理由により断念しました。

1. エンドポイントが持てるバージョンの情報は1つしかない

agentRuntimeVersion
The version of the AgentCore Runtime to use for the endpoint.
Pattern: ([1-9][0-9]{0,4})
Required: No

CreateAgentRuntimeEndpoint – Amazon Bedrock AgentCore Control API Reference

新旧のエンドポイントを用意しておくことはできますが、どちらを呼ぶかを決めるのはクライアントとなり、デプロイする側から切り替えることはできません。

2. CodeDeployの対象にはAgentCoreが入っていない

CodeDeploy is able to deploy applications to three compute platforms:
+ EC2/On-Premises … + AWS Lambda … + Amazon ECS

What is CodeDeploy? – AWS CodeDeploy User Guide

対象は、EC2、Lambda、ECSの3つだけなので、AgentCoreは対応されていません。

実現するための構成と判断理由

前段にLambdaを置く

やはりここはLambdaをはさむしかないのではと考え、Lambdaのエイリアスで切り替える構成を考えました。

Lambdaのバージョンは環境変数も固定されます。ブルーとグリーンのLambdaバージョンが、それぞれ別のAgentCoreエンドポイントを見るように環境変数を設定しておきます。
エイリアスを戻せば、その瞬間に全リクエストが元に戻ります。

エイリアスのバージョン更新がCodeDeployを起動するので、cdk deploy がそのままブルーグリーンデプロイになり、GitHub Actionsから叩くだけで済みます。

トラフィックは一度に切り替える

CodeDeployには、時間をかけて切り替える設定(LambdaCanary10Percent5Minutesなど)もありますが、こちらは採用しませんでした。

その待ち時間の間、不具合のあるバージョンが実際のユーザーに応答しているからです。
カナリアは一部の人に見せることで検知の時間を稼ぐ方式なので、今回の「公開する前に止める」という要件は満たせません。

そこで ALL_AT_ONCE にしました。検証を通らなかったものは1リクエストも通さず、通ったものは一度に切り替える方式です。

判定はアラームではなくフックで行う

デプロイの合否をCloudWatchアラームで検知するのは、ECSのブルーグリーンでも使う手法ですが今回は使えません。

アラームが見るのはメトリクスなので、トラフィックが流れてきたあとにしか検知できないからです。
ECSにはベイク時間という、切り替えたあとも旧リビジョンを残して見張る期間があります。
アラームによるロールバックは、この時間があるから成立します。逆に言えば、その間ユーザーには影響が出ています。

bakeTimeInMinutes
The time period when both blue and green service revisions are running simultaneously after the production traffic has shifted.
You must provide this parameter when you use the BLUE_GREEN deployment strategy.

DeploymentConfiguration – Amazon ECS API Reference

Amazon ECS blue/green service deployments workflow – Amazon Elastic Container Service

ALL_AT_ONCE にはこの待ち時間がないため、BeforeAllowTraffic フックを使うことにしました。
CodeDeployはフックが結果を返すまでデプロイを完了させないので、エイリアスが動く前に確かめて、失敗を返せば公開されません。
テストリクエストを5秒間隔で投げて、3回連続で失敗したら Failed を返す作りにしました。

検証はAPI Gateway越しに叩く

フックの叩き先は、新しいLambdaバージョンではなく経路全体にしました。
本番用のエイリアス(live)とは別に検証用の test を立て、API Gatewayにも /test を生やして、そちら越しに叩きます。

Lambda単体で正しく動いていても、API Gatewayを通すと不具合があることがあるからです。

実際、200が返るのに本文だけが空になったときも、 aws lambda invoke では正しい応答が出ていました。

ECSのブルーグリーンなら、この経路はCodeDeployが用意してくれます。
本番用とテスト用の2つのリスナーを構成しておくと、テスト用のほうが先に置き換えタスクセットへトラフィックを流すので、AfterAllowTestTraffic フックで検証できます。

Tutorial: Deploy an Amazon ECS service with a validation test – AWS CodeDeploy

しかし、Lambdaのデプロイに AfterAllowTestTraffic はありません。使えるフックは BeforeAllowTrafficAfterAllowTraffic の2つだけです。
なので、テスト用の経路も自分で用意することにしました。ECSなら標準で付いてくるものを自前で埋めていく感じです。

全体の構成

ここまでの判断をまとめると、こうなります。切り替えが起きる直前、BeforeAllowTraffic が動いている瞬間の状態です。

livetest が別々のAgentCoreエンドポイントを指しているところがミソ(MISO)です。
フックは test 側だけを叩くので、判定が終わるまで利用者には新しいバージョンが見えません。

CDK実装

L3コンストラクトにまとめたので、使う側はこれだけです。

中で何をしているかを順に見ていきます。

エンドポイントはデプロイごとに作る

固定名をひとつ使い回すと、新しいバージョンが発行されたタイミングでブルーとグリーンが同じものを指してしまいます。

buildId にはコミットハッシュを渡します。
デプロイのたびに名前が一意になりますし、プロキシがこの値を応答ヘッダ(x-proxy-build-id)に載せるので、いま動いているのがどのコミットかを外から辿れます。

ep_ を頭に付けているのは、エンドポイント名に数字始まりの文字列を指定できないという制約があるからです。
コミットハッシュは数字から始まることがあるので、プリフィックスをつけることで対応しました。また、ハイフンも使えないため replace で変換しています。

versionagentRuntimeVersion を渡しているのも大事なところです。
ここを '1' のようなリテラルで固定すると、エージェントを更新してもエンドポイントが古いバージョンを指したままになってしまいます。

エイリアスを2つ立てる

どちらも同じ「今回のバージョン」を指していますが、更新されるタイミングが違います。
live はCodeDeployが切り替えるまで古いバージョンを指したままですが、test はCloudFormationがそのまま更新するので、先に新しいバージョンを指します。
この違いを利用すると、切り替え前の検証に使うことができます。

currentVersion は、環境変数を含む変更のたびに新しいバージョンを発行してくれます。

CodeDeployにフックを渡す

autoRollback にアラーム連動(deploymentInAlarm)は入れていません。
合否はフックが持つので failedDeployment だけで足ります。

grantPutLifecycleEventHookExecutionStatus を忘れると、フックが判定できてもCodeDeployに伝えられません。

検証用の経路を追加する

Cognito認証ではなくIAM認証にしているのは、呼び出すのがフックのLambdaでユーザーではないからです。
外部から到達できるURLが1つ増えるので、認証を付けておきます。

本番側の統合先も必ずエイリアスにします。
バージョンや $LATEST を直接指定すると、CodeDeployがエイリアスを切り替えても呼び出し先が変わらず、ブルーグリーンが成立しません。

更新順序を宣言する

CodeDeployのデプロイは live の更新と同時に走ります。
そのタイミングで揃っていないと成立しないものが以下の4つです。

依存先 ないとどうなるか
デプロイグループのサービスロール フックを呼べずにデプロイが失敗する
フック本体とその権限 疎通が403になり、壊れていないのに失敗と判定する
AgentCoreエンドポイント READYでないと疎通がResourceNotFoundになる
test エイリアス 旧バージョンを検証して、そのまま合格する

フックのコード

IAM認証なので、SigV4で署名して投げます。

クレデンシャルは defaultProvider() に任せています。
Lambdaの実行ロールから渡ってくる一時的なものなので、キャッシュと再取得はSDKに任せたほうが楽です。

また、レスポンスストリーミングを返すエージェントの場合、API Gatewayの転送モードが既定の BUFFERED のままだと、Lambda単体では正しく応答しているのにAPI Gateway越しでは本文が空になります。
そういうケースを拾うために、200でも本文が空なら失敗にしています。

判定結果はCodeDeployに返します。

これを返し忘れると、デプロイがフックのタイムアウト(既定1時間)まで待ち続けます。
途中で例外を投げて抜けるパスを作らないよう、失敗も戻り値で表現するようにしました。

プロキシのコード

ブルーグリーンのポイントはこの部分です。

どのAgentCoreエンドポイントを呼ぶかは環境変数で決まります。Lambdaのバージョンは環境変数も固定されるので、エイリアスを戻せば、参照先のAgentCoreバージョンごと戻ります。

応答には自分のビルドIDを返すようにしておきます。

検証用のエージェント側も本文にビルドIDを入れてあるので、ヘッダと本文が一致しているかを見れば、エンドポイントの参照が正しいかを確認できます。
ここがズレていたら、どこかで古いバージョンを指しているということになります。

なお、エージェントが5XXを返した場合はここに到達しません。
SDKが RuntimeClientError を投げるので、Lambdaのエラーになり、API Gatewayが502を返します。
フックはその502で失敗を検知します。

コード全文

抜粋だけだと繋がりが見えにくいので、全文も置いておきます。

使う側のスタック(lib/bluegreen-verify-stack.ts / 42行)

L3コンストラクト(lib/blue-green-agent-api.ts / 173行)

プロキシ(lambda/proxy/index.ts / 57行)

BeforeAllowTraffic フック(lambda/hook/pre-traffic.ts / 106行)

結果

500を返すバージョンをデプロイすると、ライフサイクルイベントはこうなりました。

フックのログはこんな感じです。

AllowTraffic がSkippedです。エイリアスは一度も動いていません。
実測したところ、339回叩いてすべて200でした。
cdk deploy は失敗で終わり、今回のビルドで作ったエンドポイントも削除されています。
ちなみに正常時のデプロイ時間は270秒ぐらいでした。

まとめ

「不具合のあるビルドを公開する前に止める」ということを実現できました。ポイントは2つです。

  • BeforeAllowTraffic で判定する。 トラフィックを流したあとに気づく方式だと、気づくまでの間ユーザーに影響が出ます。切り替える前に判定すれば、その時間がゼロになります
  • 検証はAPI Gateway越しに行う。 Lambdaを直接呼ぶ形だと、その手前の設定ミスを見逃します。test エイリアスと /test を用意したぶん構成は増えましたが、経路全体を通せるようになりました

一方で、止められるのは決まった入力で再現するものだけです。
特定の入力や負荷でだけ落ちるものは、この仕組みでは分かりません。
冒頭に挙げたもう1つ、HTTP 200を返しながら品質が落ちているケースも同じで、疎通確認だけでは検知できません。

振り返ってみると、AgentCore固有の工夫はエンドポイントの扱いくらいで、CodeDeployの普通のブルーグリーンと考え方は同じでした。
AIエージェントだからといって特別な仕組みを持ち出す必要はなく、やはりここはビルディングブロックの考え方が活きたと言えるでしょう。

お問い合わせ先

執筆者プロフィール

Hayakawa Masafumi
Hayakawa MasafumiTDI デジタルイノベーション技術部
昔も今も新しいものが大好き!
インフラからアプリまで縦横無尽にトータルサポートや新技術の探求を行っています。
週末はときどきキャンプ場に出没します。
2024-2026 Japan AWS All Certifications Engineer
2026 Japan AWS Top Engineer

関連記事